/// <license>
/// Copyright (c) 2002-2008, DotNetNuke Corporation
/// All rights reserved
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
/// documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
/// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
/// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all copies or substantial portions 
/// of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
/// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
/// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
/// DEALINGS IN THE SOFTWARE.
/// </license>


#region Using Statements

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using System.Web.Compilation;
using System.Security.Permissions;

using DotNetNuke.ModuleToolkit.Common;
using DotNetNuke.ModuleToolkit.Modules.Extensions;
using DotNetNuke.ModuleToolkit.Modules.Skinning.SkinObjects;

#endregion

namespace DotNetNuke.ModuleToolkit.Modules.Skinning
{
    /// -----------------------------------------------------------------------------
    /// <summary>
    /// The SkinnedControlHost class provides a control to host a SkinnedControl
    /// </summary>
    /// <remarks>
    /// This class handles all of the necissary functionality to locate and load
    /// the skinned control.
    /// </remarks>
    /// <history>
    ///     [anurse]    05/02/2006  Created
    /// </history>
    /// -----------------------------------------------------------------------------
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal),
    AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal),
    ToolboxData("<{0}:SkinnedControlHost runat=server></{0}:SkinnedControlHost>")]
    public class SkinnedControlHost : CompositeControl
    {

        #region Private Fields

        private string _skinsFolder;
        private string _controlRelativePath;
        private Control _parentControl;
        private string _skinName;
        private string _fallbackSkinName;
        private SkinnedControl _loadedControl;
        private bool isReskinningSuspended = false;
        private bool reskinOnResume = false;
        private bool initialized = false;

        #endregion

        #region Public Properties

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Gets and sets the relative path within the skins folder to search for the control. 
        /// Causes a Reskin unless the <see cref="SuspendReskinning"/> method
        /// is active
        /// </summary>
        /// <remarks>
        /// This path should lead to an ASCX file
        /// </remarks>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        [Category("Skinning"), Description("The path to the control within the skin folder")]
        public string ControlRelativePath
        {
            get { return _controlRelativePath; }
            set
            {
                _controlRelativePath = value;
                this.ChildControlsCreated = false;
                Reskin();
            }
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Gets and sets the fallback skin that will be used to locate the skinned control
        /// if the control does not exist in the current skin. Causes a Reskin unless the 
        /// <see cref="SuspendReskinning"/> method is active
        /// </summary>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        [Category("Skinning"), Description("The name of the fallback skin")]
        public string FallbackSkinName
        {
            get
            {
                if (String.IsNullOrEmpty(_fallbackSkinName))
                    return "_default";
                else
                    return _fallbackSkinName;
            }
            set
            {
                _fallbackSkinName = value;
                this.ChildControlsCreated = false;
                Reskin();
            }
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Gets the control that was loaded by the skinning engine
        /// </summary>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        [Browsable(false)]
        public SkinnedControl LoadedControl
        {
            get
            {
                EnsureLoadedControl();
                return _loadedControl;
            }
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Gets the control that this host is embedded on
        /// </summary>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        [Category("Skinning"), Description("The control that this host is embedded on")]
        public Control ParentControl
        {
            get { return _parentControl; }
            set { _parentControl = value; }
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Gets a list of Extensions contained in the skinned control that was loaded
        /// into this host
        /// </summary>
        /// <history>
        ///     [cnurse]  10/11/2007  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        [Browsable(false)]
        public IList<IExtension> Extensions
        {
            get
            {
                if (LoadedControl == null)
                    return null;
                return LoadedControl.Extensions;
            }
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Gets and sets the current skin that will be used to locate the skinned control. 
        /// Causes a Reskin unless the <see cref="SuspendReskinning"/> method
        /// is active
        /// </summary>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        [Category("Skinning"), Description("The name of the current skin")]
        public string SkinName
        {
            get
            {
                if (String.IsNullOrEmpty(_skinName))
                    return String.Empty;
                else
                    return _skinName;
            }
            set
            {
                _skinName = value;
                this.ChildControlsCreated = false;
                Reskin();
            }
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Gets a list of skin objects contained in the skinned control that was loaded
        /// into this host
        /// </summary>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        [Browsable(false)]
        public IList<ISkinObject> SkinObjects
        {
            get
            {
                if (LoadedControl == null)
                    return null;
                return LoadedControl.SkinObjects;
            }
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Gets and sets the root skins folder. Causes a Reskin unless the <see cref="SuspendReskinning"/> method
        /// is active
        /// </summary>
        /// <remarks>
        /// This folder should contain subfolders whose names represent the available skins
        /// </remarks>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        [Category("Skinning"), Description("The root skins folder")]
        public string SkinsFolder
        {
            get { return _skinsFolder; }
            set
            {
                _skinsFolder = value;
                this.ChildControlsCreated = false;
                Reskin();
            }
        }

        #endregion

        #region Public Methods

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Finds the control with the specified id within the loaded <see cref="SkinnedControl"/>
        /// </summary>
        /// <param name="id">The id of the control to find</param>
        /// <returns>The control with the specified id, or null if no control could be found</returns>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        public Control FindControlInSkin(string id)
        {
            if (LoadedControl == null)
                return null;
            else
                return LoadedControl.FindControl(id);
        }

        public SkinnedControlHost FindSkinnedControlHost()
        {
            if (LoadedControl == null)
                return null;
            else
                return ControlUtils.FindChild<SkinnedControlHost>(LoadedControl);
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Finds the control with the specified name and verifies that it is a SkinObject
        /// </summary>
        /// <param name="name">The name of the skin object to find</param>
        /// <returns>An instance of the skin object, or a null reference (Nothing in VB.net) if no such skin object exists</returns>
        /// <remarks>
        /// Note: If this method returns a null reference (Nothing in VB.net) a control with the name may exist
        /// but it does not implement the <see cref="ISkinObject"/> interface
        /// </remarks>
        /// <history>
        ///     [anurse]  05/02/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        public ISkinObject FindSkinObject(string name)
        {
            if (LoadedControl != null)
                return LoadedControl.FindSkinObject(name);
            else
                return null;
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Allows this control to reskin when certain properties are changed. Causes a Reskin
        /// </summary>
        /// <remarks>
        /// If a property that would cause a Reskin was changed while reskinning was suspended
        /// the control will be reskinned when this method is called. This method does nothing
        /// if reskinning is not suspended. NOTE: The effects of <see cref="SuspendReskinning"/>
        /// and <see cref="ResumeReskinning"/> are not nested, no matter how many calls to
        /// <see cref="SuspendReskinning"/>, only one call to <see cref="ResumeReskinning"/>
        /// is needed, and vice versa.
        /// </remarks>
        /// <history>
        ///     [anurse]  05/04/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        public void ResumeReskinning()
        {
            if (!isReskinningSuspended)
                return;

            isReskinningSuspended = false;

            if(reskinOnResume)
                DoReskin();
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Stops this control from reskinning when properties are changed
        /// </summary>
        /// <remarks>
        /// If a property that would cause a Reskin is changed, the Reskinning is postponed
        /// until ResumeReskinning is called. This method does nothing if reskinning is already 
        /// suspended. NOTE: The effects of <see cref="SuspendReskinning"/>
        /// and <see cref="ResumeReskinning"/> are not nested, no matter how many calls to
        /// <see cref="SuspendReskinning"/>, only one call to <see cref="ResumeReskinning"/>
        /// is needed, and vice versa.
        /// </remarks>
        /// <history>
        ///     [anurse]  05/04/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        public void SuspendReskinning()
        {
            isReskinningSuspended = true;
        }

        #endregion

        #region Private Methods

        private bool CheckFields()
        {
            SkinnedControlHost host = ControlUtils.FindParent<SkinnedControlHost>(this);
            if (host != null)
            {
                if (String.IsNullOrEmpty(SkinsFolder))
                    SkinsFolder = host.SkinsFolder;
                if (String.IsNullOrEmpty(SkinName))
                    SkinName = host.SkinName;
                if (String.IsNullOrEmpty(FallbackSkinName) && (this.ParentControl is SkinnedControl))
                    FallbackSkinName = host.FallbackSkinName;
            }

            return !String.IsNullOrEmpty(SkinsFolder) && !String.IsNullOrEmpty(FallbackSkinName) && !String.IsNullOrEmpty(ControlRelativePath);
        }

        private void DoReskin()
        {
            // We are reskinning, so we don't need to queue up reskins
            reskinOnResume = false;

            // Clear the loadedControl
            _loadedControl = null;
            Controls.Clear();

            // Recreate Child Controls
            this.RecreateChildControls();
        }

        private void EnsureLoadedControl()
        {
            if ((HttpContext.Current != null) && (_loadedControl == null) && CheckFields())
            {
                string mappedPath = SkinUtil.MapSkinPath(SkinsFolder, SkinName, FallbackSkinName, ControlRelativePath);
                _loadedControl = BuildManager.CreateInstanceFromVirtualPath(mappedPath, typeof(SkinnedControl)) as SkinnedControl;
                _loadedControl.Host = this;
            }
        }

        private void Reskin()
        {
            if (!initialized)
                return;
            if (!isReskinningSuspended)
                DoReskin();
            else
                reskinOnResume = true;
        }

        #endregion

        #region Protected Methods

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Runs on when the control is initialized
        /// </summary>
        /// <param name="e">The arguments to the <see cref="Control.Init"/> event</param>
        /// <history>
        ///     [anurse]  05/04/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            initialized = true;
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Creates the controls contained within this control
        /// </summary>
        /// <history>
        ///     [anurse]  05/04/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        protected override void CreateChildControls()
        {
            if (_loadedControl == null)
                EnsureLoadedControl();
            if (_loadedControl != null)
                Controls.Add(_loadedControl);
        }

        /// -----------------------------------------------------------------------------
        /// <summary>
        /// Renders the control to the specified <see cref="HtmlTextWriter"/>
        /// </summary>
        /// <param name="writer">The <see cref="HtmlTextWriter"/> to render the control to</param>
        /// <history>
        ///     [anurse]  05/04/2006  Created
        /// </history>
        /// -----------------------------------------------------------------------------
        protected override void RenderContents(HtmlTextWriter writer)
        {
            if (HttpContext.Current == null)
            {
                writer.Write("[SkinnedControlHost: ");
                writer.Write(this.ID);
                writer.Write("]");
            }
            else
            {
                base.RenderContents(writer);
            }
        }

        #endregion

    }
}
